package com.workday.gridsimulator

import akka.actor.Actor
import akka.actor.ActorSystem
import akka.actor.ActorRef
import akka.actor.Props
import akka.pattern.ask
import akka.dispatch.Await
import akka.util.duration._
import akka.util.Timeout
import akka.event.Logging
import akka.actor.InvalidActorNameException

/**
 * This is the "driver" actor.
 * 
 * It creates the main actors in a simulation - Clock, Grid, WorkerScheduler and kicks off a simulation.
 * 
 * @param workCreater the grid delegates worker creation to this
 * 
 * @param schedulerSubmisssionLimit the work scheduler actor uses this for its work submission to the grid
 */
class GridSim(workCreater: WorkCreation, schedulerSubmissionLimit: Int) extends Actor {

  val log = Logging(context.system.eventStream, self.path.toString())

  var registerForUpdates: Boolean = false
  val actorSystem = context.system

  implicit val timeout = Timeout(5 seconds)
  var clock: ActorRef = null
  var grid: ActorRef = null
  
  try {
    clock = actorSystem.actorOf(Props[Clock], name = "clock")
    grid = actorSystem.actorOf(Props(new Grid(clock, fifoAllocator, SimpleJobWorkerCreation)), name = "staticGrid")
  } catch {
    case e: InvalidActorNameException =>
      //clock and grid actors are created already
      //send some messages to them in order to have a clean environment for simulation
      clock = actorSystem.actorFor("akka://application/user/clock")
      while (getClockStatus.equals("RUNNING")) clock ! Stop
      grid = actorSystem.actorFor("akka://application/user/staticGrid")
      grid ! "Prepare"
  }
  val fifoAllocator = new FifoAllocator
  var workersReggedWithGrid = false

  var count = 0

  def main(args: Array[String]): Unit = {
    
    if (registerForUpdates) {
      var simType: String = ""
      var noOfWorkers: Int = 0
      var workCount: Int = 0
      var arrivalRate: Int = 0
      var avgLength: Double = 0.0
      var instanceSize: String = ""

      if (args.size < 5) {
        simType = "static"
        noOfWorkers = 10
        workCount = 10
        arrivalRate = 5
        avgLength = 250.0
        instanceSize = "small"
      } else {
        //fall back to some default values....
        simType = args(0)
        noOfWorkers = args(1).toInt
        workCount = args(2).toInt
        arrivalRate = args(3).toInt
        avgLength = args(4).toDouble
        instanceSize = args(5)
      }
      var workScheduler: ActorRef = null
      try {
        workScheduler = actorSystem.actorOf(Props(new WorkScheduler(clock, grid, schedulerSubmissionLimit)), name = "workScheduler")
        workScheduler ! SetSchedulerWork(workCreater.generateWork(avgLength, workCount, arrivalRate), arrivalRate)
      } catch {
        case e: InvalidActorNameException =>
          workScheduler = actorSystem.actorFor("akka://application/user/workScheduler")
          workScheduler ! SetSchedulerWork(workCreater.generateWork(avgLength, workCount, arrivalRate), arrivalRate)
      }

      val ec2InstanceType = Ec2Instance(instanceSize)
      grid ! CreateWorkers(noOfWorkers, ec2InstanceType)

      if (simType.equalsIgnoreCase("autoSize")) grid ! "AutoSize"
      clock ! AddMe(workScheduler)

      clock ! AddMe(grid)

      //all workers regged with clock
      println("waiting for " + (noOfWorkers + 2) + " to reg with clock ")
      while (checkClockReady(noOfWorkers) == false) println("waiting for all actors to register with clock")

      //all workers regged with grid
      while (checkGridReady(noOfWorkers) == false) {
        println("waiting for workers to reg with grid")
      }

      println("LETS GO")
      clock ! Start(0.0)

    }
  }

  def checkClockReady(noOfWorkers: Int) = {
    val actorCountFuture = clock ? ActorCount
    val actorCount = Await.result(actorCountFuture, timeout.duration).asInstanceOf[Int]
    //  println("actor count is " + actorCount)
    if (actorCount == (noOfWorkers + 2)) true
    else false
  }

  def checkGridReady(noOfWorkers: Int) = {
    val actorCountFuture = grid ? AllWorkerCount
    val actorCount = Await.result(actorCountFuture, timeout.duration).asInstanceOf[Int]
    //println("actor count is " + actorCount)
    if (actorCount == noOfWorkers) true
    else false
  }

  def getClockStatus: String = {
    val clockRunningFuture = clock ? IsClockRunning
    val clockRunning = Await.result(clockRunningFuture, timeout.duration).asInstanceOf[Boolean]

    if (clockRunning) "RUNNING" else "FINISHED"
  }

  def receive = {
    case "Prepare" =>
      clock ! Stop
      grid ! "Prepare"
      
    case StartSim(args) =>
      main(args)
      
    case RegMeForUpdates(actor, updateAfterThisManyCompletions) =>
      grid ! RegMeForUpdates(actor, updateAfterThisManyCompletions)
      registerForUpdates = true
      
    case "WorkersRegistered" =>
      workersReggedWithGrid = true
      println("grid is ready")
      
    case _ =>
      println(sender + " sent msg to sgs")
  }

}

